package jp.co.nes.awssupport.business.utility;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.GZIPInputStream;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.services.s3.model.S3Object;

import jp.co.nes.awssupport.business.bean.DescriptionInfoBean;
import jp.co.nes.awssupport.business.bean.DescriptionInfoContainerBean;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.utility.AppInfoUtil;
import jp.co.nes.awssupport.common.utility.AwsS3Helper;
import jp.co.nes.awssupport.common.utility.YamlUtil;

/**
 * 説明情報ヘルパーです。
 */
public class DescriptionInfoHelper {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(DescriptionInfoHelper.class);

    /** 説明情報を格納するために使用する内容をあらわす列挙型 */
    private static enum DescriptionInfoUseService {
        S3, CLASS_PATH
    }

    /** 説明情報マップ */
    private Map<String, DescriptionInfoBean> descriptionInfoMap;

    /**
     * デフォルトコンストラクタです。
     */
    public DescriptionInfoHelper() {
    }

    /**
     * 説明情報をロードします。
     */
    public void load() {
        String category = "DescriptionInfo";
        String useServiceValue = AppInfoUtil.getVal(category, "UseService", DescriptionInfoUseService.S3.name());
        if (logger.isDebugEnabled()) {
            logger.debug("DescriptionInfoUseService=" + useServiceValue);
        }
        DescriptionInfoUseService useService = DescriptionInfoUseService.valueOf(useServiceValue);

        InputStream inputStream = null;
        if (useService == DescriptionInfoUseService.S3) {
            String bucketName = AppInfoUtil.getValAndEmptyChk(category, "BucketName");
            String objectKey = AppInfoUtil.getValAndEmptyChk(category, "ObjectKey");

            logger.debug("S3: getDescriptionInfoInputStreamFromS3 start");
            if (objectKey.endsWith(".gz")) {
                InputStream in = getDescriptionInfoInputStreamFromS3(bucketName, objectKey);
                try {
                    inputStream = new GZIPInputStream(in);
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            } else {
                inputStream = getDescriptionInfoInputStreamFromS3(bucketName, objectKey);
            }
            logger.debug("S3: getDescriptionInfoInputStreamFromS3 end");
        } else if (useService == DescriptionInfoUseService.CLASS_PATH) {
            String fileName = AppInfoUtil.getValAndEmptyChk(category, "FileName");
            if (fileName.endsWith(".gz")) {
                InputStream in = ClassLoader.getSystemResourceAsStream(fileName);
                try {
                    inputStream = new GZIPInputStream(in);
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            } else {
                inputStream = ClassLoader.getSystemResourceAsStream(fileName);
            }
        }

        if (inputStream != null) {
            try {
                DescriptionInfoContainerBean bean = YamlUtil.convertYamlInputStreamToBean(inputStream,
                        DescriptionInfoContainerBean.class);
                descriptionInfoMap = bean.getDescriptionInfoMap();
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            }
        }
    }

    /**
     * 製品名、キーに対応する説明を返却します。<br>
     * 対応する説明がない場合は、nullを返却します。
     *
     * @param productName 説明情報を取得する際に使用する製品名
     * @param key 説明を取得する際に使用するキー
     * @return 説明
     */
    public String get(String productName, String key) {
        if (descriptionInfoMap == null) {
            // 説明情報マップが読み込まれていない場合
            return null;
        }

        // 以下の処理は、製品名に対応する説明情報、デフォルトの説明情報という順番で、処理を実施する。
        // なお、製品名に対応する説明情報、デフォルトの説明情報に同じ内容が設定されていた場合、
        // 製品名に対応する説明情報に設定されている内容を優先する。
        String result = null;
        String[] keyForDescriptionInfoMapArray = getKeyForDescriptionInfoMapArray(productName);
        for (String keyForDescriptionInfoMap : keyForDescriptionInfoMapArray) {
            DescriptionInfoBean descriptionInfo = descriptionInfoMap.get(keyForDescriptionInfoMap);
            if (descriptionInfo == null) {
                // 説明情報が存在しない場合
                break;
            }

            Map<String, String> descriptionMap = descriptionInfo.getDescriptionMap();
            if (descriptionMap == null) {
                // 説明情報に、説明マップが存在しない場合
                continue;
            }

            // 説明マップからキーに対応する説明を取得する。
            result = descriptionMap.get(key);
            if (result != null) {
                // 対応する説明が存在した場合
                break;
            }
        }
        return result;
    }

    /**
     * 製品名、lineItemDescription、キーに対応する説明(ボリューミングプラン用)を返却します。<br>
     * 対応する説明がない場合は、nullを返却します。
     *
     * @param productName 説明情報を取得する際に使用する製品名
     * @param lineItemDescription 説明を取得する際に使用するlineItemDescription
     * @param key 説明を取得する際に使用するキー
     * @return 説明
     */
    public String getForVolumingPlan(String productName, String lineItemDescription, String key) {
        if (descriptionInfoMap == null) {
            // 説明情報マップが読み込まれていない場合
            return null;
        }

        // 以下の処理は、製品名に対応する説明情報、デフォルトの説明情報という順番で、処理を実施する。
        // なお、製品名に対応する説明情報、デフォルトの説明情報に同じ内容が設定されていた場合、
        // 製品名に対応する説明情報に設定されている内容を優先する。
        String result = null;
        String[] keyForDescriptionInfoMapArray = getKeyForDescriptionInfoMapArray(productName);
        for (String keyForDescriptionInfoMap : keyForDescriptionInfoMapArray) {
            DescriptionInfoBean descriptionInfo = descriptionInfoMap.get(keyForDescriptionInfoMap);
            if (descriptionInfo == null) {
                // 説明情報が存在しない場合
                break;
            }

            Map<String, Map<String, String>> descriptionMap = descriptionInfo.getDescriptionMapForVolumingPlan();
            if (descriptionMap == null) {
                // 説明情報に、説明マップ(ボリューミングプラン用)が存在しない場合
                continue;
            }

            // 説明マップからキーに対応し、説明のキーが、lineItemDescriptionと部分一致する説明を取得する。
            Map<String, String> map = descriptionMap.get(key);
            Set<Entry<String, String>> entrySet = map.entrySet();
            for (Entry<String, String> entry : entrySet) {
                String entryKey = entry.getKey();
                if (!StringUtils.isEmpty(lineItemDescription) && lineItemDescription.indexOf(entryKey) >= 0) {
                    // 説明のキーが、lineItemDescriptionと部分一致した場合
                    result = entry.getValue();
                    break;
                }
            }
        }
        return result;
    }

    /**
     * s3から説明情報を読み込み、入力ストリームを返却します。
     *
     * @param bucketName バケット名
     * @param objectKey オブジェクトキー
     * @return 入力ストリーム
     */
    private InputStream getDescriptionInfoInputStreamFromS3(String bucketName, String objectKey) {
        AwsS3Helper helper = new AwsS3Helper();
        S3Object s3Object = helper.getObject(bucketName, objectKey);
        return s3Object.getObjectContent();
    }

    /**
     * 説明情報を取得する際に使用するキー名の配列を返却します。<br>
     * 配列の順番は、製品名に対応する説明情報を取得する際に使用するキー名、
     * デフォルトの説明情報を取得する際に使用するキー名という順番にします。
     *
     * @param productName 説明情報を取得する際に使用する製品名
     * @return 説明情報を取得する際に使用するキー名の配列
     */
    private String[] getKeyForDescriptionInfoMapArray(String productName) {
        return new String[] {
                getKeyForDescriptionInfoMap(productName),
                getKeyForDescriptionInfoMap(DescriptionInfoContainerBean.DEFAULT)
        };
    }

    /**
     * 説明情報を取得する際に使用するキー名を返却します。<br>
     * 本処理は、説明情報を定義しているyamlファイルのキー名に、
     * 製品名の末尾に空白がある「Contact Center Telecommunications (service sold by AMCS, LLC) 」を
     * 空白を含めて定義できなかったので、「Contact Center Telecommunications (service sold by AMCS, LLC) 」場合は、
     * 製品名の末尾から空白を除去する処理を実施しています。
     *
     * @param productName 説明情報を取得する際に使用する製品名
     * @return 説明情報を取得する際に使用するキー名
     */
    private String getKeyForDescriptionInfoMap(String productName) {
        if ("Contact Center Telecommunications (service sold by AMCS, LLC) ".equals(productName)) {
            return productName.trim();
        }
        return productName;
    }
}
